/*
 * Copyright 2011 Sven Buschbeck.
 * 
 * This file is part of the ActiveObjects library.
 *
 *  ActiveObjects is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  ActiveObjects is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.svenbuschbeck.gwt.ao.client;

import net.svenbuschbeck.gwt.ao.client.events.ValueChangedEvent;
import net.svenbuschbeck.gwt.ao.client.events.ValueChangedHandler;

/**
 * Wrapper for any kind of variable that should be watched for changes, whenever
 * the variable gets changed, an ValueChangeEvent gets fired.
 * 
 * @author Sven Buschbeck
 * 
 * @param <T>
 *            Type of variable to be wrapped and watched for changes
 */
public class ActiveVariable<T> extends ChangedHandler<T> {

	public abstract class ReadOnlyVariableHook implements ReadOnlyHook<T> {
		public void commitValue(T value) {
			set(value);
		}
	}

	//	private T value;
	//	private HandlerManager manager = new HandlerManager(null);
	private final ReadOnlyVariableHook readOnlyHook;

	/**
	 * Creates an ActiveVariable object initialized with null value.
	 */
	public ActiveVariable() {
		this((T) null);
	}

	/**
	 * Creates an ActiveVariable object initialized with the given value.
	 * 
	 * @param initialValue
	 *            Value to initialize this ActiveVariable with.
	 */
	public ActiveVariable(T initialValue) {
		this(initialValue, (ReadOnlyVariableHook) null);
	}

	/**
	 * Creates an ActiveVariable object initialized with the given value.
	 * 
	 * @param initialValue
	 *            Value to initialize this ActiveVariable with.
	 */
	public ActiveVariable(ReadOnlyVariableHook readOnlyHook) {
		this.readOnlyHook = readOnlyHook;
	}

	/**
	 * Creates an ActiveVariable object initialized with the given value.
	 * 
	 * @param initialValue
	 *            Value to initialize this ActiveVariable with.
	 */
	public ActiveVariable(T initialValue, ReadOnlyVariableHook readOnlyHook) {
		this.readOnlyHook = readOnlyHook;
		set(initialValue);
	}

	/**
	 * Creates an ActiveVariable object initialized with the given value and
	 * adds the given handler *afterwards*.
	 * 
	 * @param initialValue
	 *            Value to initialize this ActiveVariable with.
	 * @param valueChangeHandler
	 *            Gets informed when the given value changes
	 */
	public ActiveVariable(T initialValue, ValueChangedHandler<T> valueChangedHandler) {
		this(initialValue, (ReadOnlyVariableHook) null);
		addValueChangedHandler(valueChangedHandler);
	}

	/**
	 * Creates an ActiveVariable object with the given handler attached to it.
	 * *Afterwards* the given initial gets set causing the handler to be
	 * informed unless the given value equal null.
	 * 
	 * @param initialValue
	 *            Value to initialize this ActiveVariable with.
	 * @param valueChangeHandler
	 *            Gets informed when the given value changes
	 */
	public ActiveVariable(ValueChangedHandler<T> valueChangedHandler, T initialValue) {
		this(initialValue, (ReadOnlyVariableHook) null);
		addValueChangedHandler(valueChangedHandler);
	}

	/**
	 * Creates an ActiveVariable object with the given handler attached to it.
	 * *Afterwards* the given initial gets set causing the handler to be
	 * informed unless the given value equal null.
	 * 
	 * @param initialValue
	 *            Value to initialize this ActiveVariable with.
	 * @param valueChangeHandler
	 *            Gets informed when the given value changes
	 */
	public ActiveVariable(T initialValue, ValueChangedHandler<T> valueChangedHandler, ReadOnlyVariableHook readOnlyHook) {
		this();
		addValueChangedHandler(valueChangedHandler);
		set(initialValue);
	}

	/**
	 * Returns the value of the wrapped variable.
	 * 
	 * @return Wrapped variable's value.
	 */
	public T get() {
		return value;
	}

	/**
	 * If newValue is different than the current value, an ValueChangeEvent get
	 * fired.
	 * 
	 * @param newValue
	 */
	public boolean set(T newValue) {
		return set(newValue, false);
	}

	/**
	 * If newValue is different than the current value or forceUpdate is set
	 * true, an ValueChangeEvent get fired.
	 * 
	 * @param newValue
	 */
	public boolean set(T newValue, boolean forceUpdate) {
		if (isReadOnly()) {
			readOnlyHook.onNewValue(newValue);
			return false;
		}
		return _set(newValue, forceUpdate);
	}

	private boolean _set(T newValue, boolean forceUpdate) {
		T oldValue = value;
		value = newValue;
		return ValueChangedEvent.fireIfNotEqual(this, oldValue, newValue, forceUpdate);
	}

	/**
	 * Causes an ValueChangeEvent to be fired. Especially useful to avoid
	 * cloning an object if only a single attribute of it has changed.
	 */
	public void forceUpdate() {
		ValueChangedEvent.fire(this, value, value);
	}

	@Override public String toString() {
		return super.toString("value=" + get());
	}

	public boolean isReadOnly() {
		return readOnlyHook != null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override public boolean equals(Object obj) {
		try {
			return obj == this || (this.value == obj || obj.equals(this.value))
				|| (this.equals((ActiveVariable<T>) obj));
		} catch (Throwable t) {
			return false;
		}
	}

	public boolean equals(ActiveVariable<T> obj) {
		return obj == value || (obj != null && obj.equals(this.value));
	}
}
